<?php

/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */

namespace Server;

use \Workerman\Worker;
use \Workerman\Lib\Timer;

require_once __DIR__ . '/Constants.php';

/**
 * 世界服务器
 * @property-read type $server ws服务器对象
 */
class WorldServer {

    public $id;
    public $maxPlayers; //最大在线人数
    public $server; // ws 服务器对象
    public $ups; // 真实
    public $map; // 地图对象
    public $entities = array();// 
    public $players = array(); // 玩家
    public $mobs = array();// 暴徒
    public $attackers = array(); //攻击
    public $items = array(); // 物品
    public $equipping = array();// 装备中
    public $hurt = array(); //使其受伤 
    public $npcs = array();// NPC们
    public $mobAreas = array();// 暴徒其余
    public $chestAreas = array(); // 物品区域
    public $groups = array();// 分组
    public $outgoingQueues = array(); //输出队列
    public $itemCount;
    public $playerCount; //玩家人数
    public $zoneGroupsReady; // 区域分组准备完毕

    /**
     * 创建服务器
     * @param type $id ID
     * @param type $maxPlayers 最大人数
     * @param type $websocketServer ws服务器对象
     */
    public function __construct($id, $maxPlayers, $websocketServer) {
        $this->id = $id;
        $this->maxPlayers = $maxPlayers;
        $this->server = $websocketServer;
        $this->ups = 50;// 帧数?
        $this->map = null;
        $this->entities = array();
        $this->players = array();
        $this->mobs = array();
        $this->attackers = array();
        $this->items = array();
        $this->equipping = array();
        $this->hurt = array();
        $this->npcs = array();
        $this->mobAreas = array();
        $this->chestAreas = array();
        $this->groups = array();

        $this->outgoingQueues = array();

        $this->itemCount = 0;
        $this->playerCount = 0;

        $this->zoneGroupsReady = false;
        $self = $this;
        // 当玩家连接的时候
        $this->onPlayerConnect(function ($player)use($self) {
            // 获取出生点 的回调函数
            $player->onRequestPosition(function()use($self, $player) {
                // 玩家的最后出生点
                if ($player->lastCheckpoint) {
                    // 最后出生点检查
                    return $player->lastCheckpoint->getRandomPosition();
                } else {
                    return $self->map->getRandomStartingPosition();
                }
            });
        });
        // 当玩家动作的时候
        $this->onPlayerEnter(
                function(\Server\Player $player) use ($self) {

            echo $player->name . " has joined " . $self->id . "\n";

            if (!$player->hasEnteredGame) {
                $self->incrementPlayerCount();
            }

            // Number of players in this world 这个世界的玩家数量
            $self->pushToPlayer($player, new Messages\Population($self->playerCount, 1));
            # 推送实体列表到
            $self->pushRelevantEntityListTo($player);

            # 移动的回调 
            $moveCallback = function($x, $y) use($player, $self) {
                echo $player->name . " is moving to (" . $x . ", " . $y . ")\n";
                # 攻击目标遍历
                $player->forEachAttacker(function(\Server\Mob $mob) use($player, $self) {
                    echo "攻击目标遍历";
                        
                    $target = $self->getEntityById($mob->target);
                    if ($target) {
                        $pos = $self->findPositionNextTo($mob, $target);
                        if ($mob->distanceToSpawningPoint($pos['x'], $pos['y']) > 50) {
                            // 距离出生点 > 50
                            // 取消 标记
                            $mob->clearTarget();
                            // 忘记所有人 
                            $mob->forgetEveryone();
                            // 移除 攻击中 的暴徒
                            $player->removeAttacker($mob);
                        } else {
                            $self->moveEntity($mob, $pos['x'], $pos['y']);
                        }
                    }
                });
            };
            // 移动的回调
            $player->onMove($moveCallback);
            // 带有仇恨的移动
            $player->onLootMove($moveCallback);
            // 区域切换
            $player->onZone(function() use($self, $player) {
                echo "on zone";
                
                $hasChangedGroups = $self->handleEntityGroupMembership($player);

                if ($hasChangedGroups) {
                    $self->pushToPreviousGroups($player, new Messages\Destroy($player));
                    $self->pushRelevantEntityListTo($player);
                }
            });
            // 广播 处理
            $player->onBroadcast(function($message, $ignoreSelf) use($self, $player) {
                // 发送给 临近的组
                $self->pushToAdjacentGroups($player->group, $message, $ignoreSelf ? $player->id : null);
            });
            // 切换 区域 广播
            $player->onBroadcastToZone(function($message, $ignoreSelf) use($self, $player) {
                // 发送给组
                $self->pushToGroup($player->group, $message, $ignoreSelf ? $player->id : null);
            });

            // 退出的时候
            $player->onExit(function() use($self, $player) {
                echo $player->name . " has left the game.\n";
                // 移除玩家
                $self->removePlayer($player);
                // 自减人数
                $self->decrementPlayerCount();

                if (isset($self->removedCallback)) {
                    call_user_func($self->removedCallback);
                }
            });

            if (isset($self->addedCallback)) {
                call_user_func($self->addedCallback);
            }
        }
        );
            
        // 实体 攻击
        $this->onEntityAttack(function(\Server\Character $attacker) use($self) {
            echo "onEntityAttack";
            // 获取目标
            $target = $self->getEntityById($attacker->target);
            // 目标存在 && 攻击者为暴徒
            if ($target && $attacker->type == "mob") {
                // 寻找追击玩家的坐标
                $pos = $self->findPositionNextTo($attacker, $target);
                // 移动暴徒的位置
                $self->moveEntity($attacker, $pos['x'], $pos['y']);
            }
        });
        // 怪物 重置 
        $this->onRegenTick(function() use ($self) {
            $self->forEachCharacter(function($character) use ($self) {
                if (!$character->hasFullHealth()) {
                    // 不满血 恢复最高血量的4%
                    $character->regenHealthBy(floor($character->maxHitPoints / 25));
                    if ($character->type == 'player') {
                        // 通知玩家
                        $self->pushToPlayer($character, $character->regen());
                    }
                }
            });
        });
    }

    /**
     * 启动这个世界
     * @param string $mapFilePath 地图文件地址
     */
    public function run($mapFilePath) {
        $self = $this;
        // 实例化 得到 地图对象
        $this->map = new Map($mapFilePath);
        // 当地图准备完成后
        $this->map->ready(function() use ($self) {
            // 初始化空间分组
            $self->initZoneGroups();
            //生成碰撞网格
            $self->map->generateCollisionGrid();

            // Populate all mob "roaming" areas
            // 构建所有的暴民(怪物)"徘徊" 区域
            foreach ($self->map->mobAreas as $a) {
                // 实例化暴徒对象区域
                $area = new MobArea($a->id, $a->nb, $a->type, $a->x, $a->y, $a->width, $a->height, $self);
                // 产生暴徒
                $area->spawnMobs();
                // @todo bind
                //$area->onEmpty($self->handleEmptyMobArea->bind($self, area));
                // 空地图
                $area->onEmpty(function() use ($self, $area) {
                    call_user_func(array($self, 'handleEmptyMobArea'), $area);
                });
                $self->mobAreas[] = $area;
            }

            // Create all chest areas
            // 构建所有箱子区域
            foreach ($self->map->chestAreas as $a) {
                // 实例化得到区域对象
                $area = new ChestArea($a->id, $a->x, $a->y, $a->w, $a->h, $a->tx, $a->ty, $a->i, $self);
                $self->chestAreas[] = $area;
                // @todo bind
                $area->onEmpty(function()use($self, $area) {
                    call_user_func(array($self, 'handleEmptyChestArea'), $area);
                });
            }

            // Spawn static chests
            // 创建静态物品
            foreach ($self->map->staticChests as $chest) {
                $c = $self->createChest($chest->x, $chest->y, $chest->i);
                $self->addStaticItem($c);
            }

            // Spawn static entities
            // 创建静态 实体
            $self->spawnStaticEntities();

            // Set maximum number of entities contained in each chest area
            // 设置每个chest区域中包含的最大实体数
            foreach ($self->chestAreas as $area) {
                $area->setNumberOfEntities(count($area->entities));
            }
        });
        // 进行地图初始化
        $this->map->initMap();
        // 加入定时器
        $regenCount = $this->ups * 2;
        $updateCount = 0;
        //
        Timer::add(1 / $this->ups, function() use ($self, $regenCount, &$updateCount) {
            $self->processGroups();
            $self->processQueues();

            if ($updateCount < $regenCount) {
                $updateCount += 1;
            } else {
                if ($self->regenCallback) {
                    call_user_func($self->regenCallback);
                }
                $updateCount = 0;
            }
        });

        echo $this->id . " created capacity: " . $this->maxPlayers . " players \n";
    }

    public function setUpdatesPerSecond($ups) {
        $this->ups = $ups;
    }

    public function onInit($callback) {
        $this->initCallback = $callback;
    }

    public function onPlayerConnect($callback) {
        $this->connectCallback = $callback;
    }

    public function onPlayerEnter($callback) {
        $this->enterCallback = $callback;
    }

    public function onPlayerAdded($callback) {
        $this->addedCallback = $callback;
    }

    public function onPlayerRemoved($callback) {
        $this->removedCallback = $callback;
    }

    public function onRegenTick($callback) {
        $this->regenCallback = $callback;
    }

    /**
     * 讲相关的实体列表 推送到 
     * @param type $player 目标
     */
    public function pushRelevantEntityListTo($player) {
        if ($player && isset($this->groups[$player->group])) {
            $entities = array_keys($this->groups[$player->group]->entities);
            $entities = Utils::reject($entities, function($id)use($player) {
                        return $id == $player->id;
                    });
            //$entities = array_map(function($id) { return intval($id); }, $entities);
            if ($entities) {
                $this->pushToPlayer($player, new Messages\Lists($entities));
            }
        }
    }
    
    /**
     * 发送一个 产卵 的消息 到玩家
     * @param type $player
     * @param type $ids
     */
    public function pushSpawnsToPlayer($player, $ids) {
        foreach ($ids as $id) {
            $entity = $this->getEntityById($id);
            if ($entity) {
                $this->pushToPlayer($player, new Messages\Spawn($entity));
            } else {
                echo new \Exception("bad id:$id ids:" . json_encode($ids));
            }
        }
    }

    /**
     * 给用户发消息
     * @param type $player
     * @param type $message
     */
    public function pushToPlayer($player, $message) {
        if ($player && isset($this->outgoingQueues[$player->id])) {
            $this->outgoingQueues[$player->id][] = $message->serialize();
        } else {
            echo "pushToPlayer: player was undefined";
        }
    }
    /**
     * 发送给组
     * @param type $groupId 组的ID
     * @param type $message 消息
     * @param type $ignoredPlayer 跳过的用户
     */
    public function pushToGroup($groupId, $message, $ignoredPlayer = null) {
        $group = $this->groups[$groupId];
        if ($group) {
            foreach ($group->players as $playerId) {
                if ($playerId != $ignoredPlayer) {
                    $this->pushToPlayer($this->getEntityById($playerId), $message);
                }
            }
        } else {
            echo "groupId: " . $groupId . " is not a valid group";
        }
    }

    /**
     * 发送到临近的组
     * @param type $groupId
     * @param type $message
     * @param type $ignoredPlayer
     */
    public function pushToAdjacentGroups($groupId, $message, $ignoredPlayer = 0) {
        $self = $this;
        $this->map->forEachAdjacentGroup($groupId, function($id) use ($self, $message, $ignoredPlayer) {
            $self->pushToGroup($id, $message, $ignoredPlayer);
        });
    }

    /**
     * 推入之前的组
     * @param type $player
     * @param type $message
     */
    public function pushToPreviousGroups($player, $message) {
        // Push this message to all groups which are not going to be updated anymore,
        // 将此消息推送到所有不再更新的组，
        // since the player left them.
        // 因为玩家离开了他们
        foreach ($player->recentlyLeftGroups as $id) {
            $this->pushToGroup($id, $message);
        }
        $player->recentlyLeftGroups = array();
    }

    /**
     * 推入广播 
     * @param type $message
     * @param type $ignoredPlayer
     */
    public function pushBroadcast($message, $ignoredPlayer = null) {
        foreach ($this->outgoingQueues as $id => $item) {
            if ($id != $ignoredPlayer) {
                $this->outgoingQueues[$id][] = $message->serialize();
            }
        }
    }
    
    /**
     * 过程队列
     */
    public function processQueues() {
        foreach ($this->outgoingQueues as $id => $item) {
            if ($this->outgoingQueues[$id]) {
                $connection = $this->server->connections[$id];
                $connection->send(json_encode($this->outgoingQueues[$id]));
                $this->outgoingQueues[$id] = array();
            }
        }
    }
    

    /**
     * 增加实体
     * @param \Server\Entity $entity
     */
    public function addEntity(\Server\Entity $entity) {
        $this->entities[$entity->id] = $entity;
        $this->handleEntityGroupMembership($entity);
    }
    
    /**
     * 移除一个实体
     * @param \Server\Entity $entity
     */
    public function removeEntity(\Server\Entity $entity) {
        unset($this->entities[$entity->id],
                $this->mobs[$entity->id],
                $this->items[$entity->id]
        );

        if ($entity->type === "mob") {
            $this->clearMobAggroLink($entity);
            $this->clearMobHateLinks($entity);
        }

        $entity->destroy();
        $this->removeFromGroups($entity);
        echo "Removed " . Types::getKindAsString($entity->kind) . " : " . $entity->id . "\n";
    }

    /**
     * 增加一个玩家
     * @param type $player
     */
    public function addPlayer(\Server\Player $player) {
        $this->addEntity($player);
        $this->players[$player->id] = $player;
        $this->outgoingQueues[$player->id] = array();
    }
    
    /**
     * 移除一个玩家
     * @param type $player
     */
    public function removePlayer(\Server\Player $player) {
        $player->broadcast($player->despawn());
        $this->removeEntity($player);
        unset($this->players[$player->id], $this->outgoingQueues[$player->id]);
    }

    /**
     * 增加一个暴徒(怪物)
     * @param type $mob
     */
    public function addMob($mob) {
        $this->addEntity($mob);
        $this->mobs[$mob->id] = $mob;
    }

    /**
     * 增加一个NPC 
     * @param type $kind 种类
     * @param type $x 位置X 
     * @param type $y 位置Y
     * @return \Server\Npc NPC对象
     */
    public function addNpc($kind, $x, $y) {
        $npc = new Npc('8' . $x . '' . $y, $kind, $x, $y);
        $this->addEntity($npc);
        $this->npcs[$npc->id] = $npc;
        return $npc;
    }

    /**
     * 增加一个物品 
     * @param \Server\Item $item 物品对象
     * @return \Server\Item 返回物品对象
     */
    public function addItem(\Server\Item $item) {
        $this->addEntity($item);
        $this->items[$item->id] = $item;

        return $item;
    }
    
    /**
     * 创建一个物品
     * @param type $kind
     * @param type $x
     * @param type $y
     * @return \Server\Item
     */
    public function createItem(int $kind, $x, $y) {
 
        $id = '9' . ($this->itemCount++);
        if ($kind == TYPES_ENTITIES_CHEST) {
            // 创建箱子
            $item = new Chest($id, $x, $y);
        } else {
            // 常见物品
            $item = new Item($id, $kind, $x, $y);
        }
        return $item;
    }

    /**
     * 创建一个箱子
     * @param type $x
     * @param type $y
     * @param type $items
     * @return type
     */
    public function createChest($x, $y, $items) {
        $chest = $this->createItem(TYPES_ENTITIES_CHEST, $x, $y);
        $chest->setItems($items);
        return $chest;
    }

    /**
     * 增加一个静态物品
     * @param type $item
     * @return type
     */
    public function addStaticItem($item) {
        $item->isStatic = true;
        $self = $this;
        // @todo bind
        //$item->onRespawn($this->addStaticItem->bind($this, $item));
        $item->onRespawn(function()use($self, $item) {
            call_user_func(array($self, 'addStaticItem'), $item);
        });

        return $this->addItem($item);
    }

    /**
     * 增加一个物品,从箱子中开出来的
     * @param type $kind
     * @param type $x
     * @param type $y
     * @return type
     */
    public function addItemFromChest($kind, $x, $y) {
        $item = $this->createItem($kind, $x, $y);
        $item->isFromChest = true;

        return $this->addItem($item);
    }

    /**
     * The mob will no longer be registered as an attacker of its current target.
     * 怪物将不再被注册为当前目标的攻击者。
     */
    public function clearMobAggroLink($mob) {
        if ($mob->target) {
            $player = $this->getEntityById($mob->target);
            if ($player) {
                $player->removeAttacker($mob);
            }
        }
    }

    /**
     * 清楚暴徒仇恨
     * @param type $mob
     */
    public function clearMobHateLinks($mob) {
        if ($mob) {
            foreach ($mob->hatelist as $obj) {
                $player = $this->getEntityById($obj->id);
                if ($player) {
                    $player->removeHater($mob);
                }
            }
        }
    }

    /**
     * 遍历 实体
     * @param type $callback
     */
    public function forEachEntity($callback) {
        foreach ($this->entities as $item) {
            call_user_func($callback, $item);
        }
    }

    /**
     * 遍历玩家
     */
    public function forEachPlayer($callback) {
        foreach ($this->players as $player) {
            call_user_func($callback, $player);
        }
    }

    /**
     * 遍历暴徒
     * @param type $callback
     */
    public function forEachMob($callback) {
        foreach ($this->mobs as $mob) {
            call_user_func($callback, $mob);
        }
    }

    /**
     * 
     * 遍历角色
     * @param type $callback
     */
    public function forEachCharacter($callback) {
        $this->forEachPlayer($callback);
        $this->forEachMob($callback);
    }

    /**
     * 处理暴徒的仇恨
     * @param type $mobId 暴徒ID
     * @param type $playerId 玩家ID
     * @param type $hatePoints 仇恨点
     */
    public function handleMobHate($mobId, $playerId, $hatePoints) {
        $mob = $this->getEntityById($mobId);
        $player = $this->getEntityById($playerId);
        if ($player && $mob) {
            $mob->increaseHateFor($playerId, $hatePoints);
            $player->addHater($mob);

            if ($mob->hitPoints > 0) { // only choose a target if still alive
                $this->chooseMobTarget($mob, 0);
            }
        }
    }

    /**
     * 选择 暴徒 目标
     * @param type $mob
     * @param type $hateRank
     */
    public function chooseMobTarget($mob, $hateRank = 0) {
        $player = $this->getEntityById($mob->getHatedPlayerId($hateRank));

        // If the mob is not already attacking the player, create an attack link between them.
        if ($player && !isset($player->attackers[$mob->id])) {
            $this->clearMobAggroLink($mob);

            $player->addAttacker($mob);
            $mob->setTarget($player);

            $this->broadcastAttacker($mob);
            echo $mob->id . " is now attacking " . $player->id . "\n";
        }
    }

    /**
     * 绑定 角色 共计 的回调
     * @param type $callback
     */
    public function onEntityAttack($callback) {
        $this->attackCallback = $callback;
    }

    /**
     * 获取角色的 ID
     * @param type $id
     * @return type
     */
    public function getEntityById($id) {
        if (isset($this->entities[$id])) {
            return $this->entities[$id];
        } else {
            echo "Unknown entity : $id\n";
        }
    }

    /**
     * 获取玩家总数
     * @return int
     */
    public function getPlayerCount() {
        $count = 0;
        foreach ($this->players as $p => $player) {
            if ($this->players->hasOwnProperty($p)) {
                $count += 1;
            }
        }
        return $count;
    }

    /**
     * 广播 攻击者
     * @param type $character 角色
     */
    public function broadcastAttacker($character) {
        if ($character) {
            $this->pushToAdjacentGroups($character->group, $character->attack(), $character->id);
        }
        if ($this->attackCallback) {
            call_user_func($this->attackCallback, $character);
        }
    }

    /**
     * 处理实体 伤害/攻击
     * @param type $entity
     * @param type $attacker
     * @param type $damage
     */
    public function handleHurtEntity($entity, $attacker = null, $damage = 0) {
        if ($entity->type === 'player') {
            // A player is only aware of his own hitpoints
            // 玩家只知道自己的生命值
            $this->pushToPlayer($entity, $entity->health());
        }

        if ($entity->type === 'mob') {
            // Let the mob's attacker (player) know how much damage was inflicted
            // 让怪物的攻击者(玩家)知道造成了多少伤害 
            $this->pushToPlayer($attacker, new Messages\Damage($entity, $damage));
        }

        // If the entity is about to die
        // 如果这个实体死亡
        if ($entity->hitPoints <= 0) {
            if ($entity->type === "mob") {
                // 暴徒死亡
                $mob = $entity;
                $item = $this->getDroppedItem($mob);

                $this->pushToPlayer($attacker, new Messages\Kill($mob));
                $this->pushToAdjacentGroups($mob->group, $mob->despawn()); // Despawn must be enqueued before the item drop
                if ($item) {
                    $this->pushToAdjacentGroups($mob->group, $mob->drop($item));
                    $this->handleItemDespawn($item);
                }
            }

            if ($entity->type === "player") {
                $this->handlePlayerVanish($entity);
                $this->pushToAdjacentGroups($entity->group, $entity->despawn());
            }

            $this->removeEntity($entity);
        }
    }

    public function despawn($entity) {
        $this->pushToAdjacentGroups($entity->group, $entity->despawn());

        if (isset($this->entities[$entity->id])) {
            $this->removeEntity($entity);
        }
    }

    /**
     * 产生 静态实体
     */
    public function spawnStaticEntities() {
        $count = 0;
        foreach ($this->map->staticEntities as $tid => $kindName) {
            $kind = Types::getKindFromString($kindName);
            $pos = $this->map->titleIndexToGridPosition($tid);

            if (Types::isNpc($kind)) {
                $this->addNpc($kind, $pos['x'] + 1, $pos['y']);
            }
            if (Types::isMob($kind)) {
                $mob = new Mob('7' . $kind . ($count++), $kind, $pos['x'] + 1, $pos['y']);
                $self = $this;
                $mob->onRespawn(function() use ($mob, $self) {
                    $mob->isDead = false;
                    $self->addMob($mob);
                    if (!empty($mob->area) && $mob->area instanceof ChestArea) {
                        $mob->area->addToArea($mob);
                    }
                });
                // @todo bind
                $mob->onMove(array($self, 'onMobMoveCallback'));
                $this->addMob($mob);
                $this->tryAddingMobToChestArea($mob);
            }
            if (Types::isItem($kind)) {
                $this->addStaticItem($this->createItem($kind, $pos['x'] + 1, $pos['y']));
            }
        }
    }

    /**
     * 是有效的位置
     * @param type $x
     * @param type $y
     * @return boolean
     */
    public function isValidPosition($x, $y) {
        if ($this->map && is_numeric($x) && is_numeric($y) && !$this->map->isOutOfBounds($x, $y) && !$this->map->isColliding($x, $y)) {
            return true;
        }
        return false;
    }

    /**
     * 处理玩家消失 
     * @param type $player
     */
    public function handlePlayerVanish($player) {
        $previousAttackers = array();
        $self = $this;
        // When a player dies or teleports, all of his attackers go and attack their second most hated $player->
        $player->forEachAttacker(function($mob) use (&$previousAttackers, $self) {
            $previousAttackers[] = $mob;
            $self->chooseMobTarget($mob, 2);
        });


        foreach ($previousAttackers as $mob) {
            $player->removeAttacker($mob);
            $mob->clearTarget();
            $mob->forgetPlayer($player->id, 1000);
        }

        $this->handleEntityGroupMembership($player);
    }

    /**
     * 设置玩家人数
     * @param type $count
     */
    public function setPlayerCount($count) {
        $this->playerCount = $count;
    }
    
    /**
     * 自增玩家人数
     */
    public function incrementPlayerCount() {
        $this->setPlayerCount($this->playerCount + 1);
    }

    /**
     * 自减 玩家人数
     */
    public function decrementPlayerCount() {
        if ($this->playerCount > 0) {
            $this->setPlayerCount($this->playerCount - 1);
        }
    }

    /**
     * 获取删除的元素
     * @param type $mob 暴徒
     * @return type
     */
    public function getDroppedItem(\Server\Mob $mob) {
        $kind = Types::getKindAsString($mob->kind);
        $drops = Properties::$properties[$kind]['drops'];
        $v = rand(0, 100);
        $p = 0;

        foreach ($drops as $itemName => $percentage) {
            $p += $percentage;
            if ($v <= $p) {
                $item = $this->addItem($this->createItem(Types::getKindFromString($itemName), $mob->x, $mob->y));
                return $item;
            }
        }
    }

    /**
     * 暴徒移动的回调
     * @param type $mob
     */
    public function onMobMoveCallback($mob) {
        $this->pushToAdjacentGroups($mob->group, new Messages\Move($mob));
        $this->handleEntityGroupMembership($mob);
    }

    /**
     * 寻找下一次移动的位置
     * @param type $entity
     * @param type $target
     * @return type
     */
    public function findPositionNextTo($entity, $target) {
        $valid = false;

        while (!$valid) {
            $pos = $entity->getPositionNextTo($target);
            $valid = $this->isValidPosition($pos['x'], $pos['y']);
        }
        return $pos;
    }

    /**
     * 初始化 空间分组
     */
    public function initZoneGroups() {
        $self = $this;
        $this->map->forEachGroup(function($id) use ($self) {
            $self->groups[$id] = (object) array('entities' => array(),
                        'players' => array(),
                        'incoming' => array()
            );
        });
        $this->zoneGroupsReady = true;
    }

    public function removeFromGroups($entity) {
        $self = $this;
        $oldGroups = array();

        if ($entity && isset($entity->group)) {
            $group = $this->groups[$entity->group];
            if ($entity instanceof Player) {
                $group->players = Utils::reject($group->players, function($id) use($entity) {
                            return $id == $entity->id;
                        });
            }

            $this->map->forEachAdjacentGroup($entity->group, function($id) use ($entity, &$oldGroups, $self) {
                if (isset($self->groups[$id]->entities[$entity->id])) {
                    unset($self->groups[$id]->entities[$entity->id]);
                    $oldGroups[] = $id;
                }
            });
            $entity->group = null;
        }
        return $oldGroups;
    }

    /**
     * Registers an entity as "incoming" into several groups, meaning that it just entered them.
     * 将一个实体注册“传入”到几个组中,这意味着他只是输入了这些组.
     * All players inside these groups will receive a Spawn message when WorldServer.processGroups is called.
     * 当WorldServer.processGroups 出现时，这些组内的所有玩家都会收到一条出现消息。
     */
    public function addAsIncomingToGroup($entity, $groupId) {
        $self = $this;
        $isChest = $entity && $entity instanceof Chest;// 箱子
        $isItem = $entity && $entity instanceof Item;// 物品
        $isDroppedItem = $entity && $isItem && !$entity->isStatic && !$entity->isFromChest;

        if ($entity && $groupId) {
            $this->map->forEachAdjacentGroup($groupId, function($id) use ($self, $isChest, $isItem, $isDroppedItem, $entity) {
                $group = $self->groups[$id];
                if ($group) {
                    if (!isset($group->entities[$entity->id])
                            //  Items dropped off of mobs are handled differently via DROP messages. See handleHurtEntity.
                            // 掉落的物品会通过掉落信息的方式处理。请见 handleHurtEntity。           
                            && (!$isItem || $isChest || ($isItem && !$isDroppedItem))) {
                        $group->incoming[] = $entity;
                    }
                }
            });
        }
    }

    /**
     * 增加 到分组
     * @param \Server\Player $entity
     * @param type $groupId
     * @return array
     */
    public function addToGroup($entity, $groupId) {
        $self = $this;
        $newGroups = array();

        if ($entity && $groupId && (isset($this->groups[$groupId]))) {
            $this->map->forEachAdjacentGroup($groupId, function($id) use ($self, &$newGroups, $entity, $groupId) {
                $self->groups[$id]->entities[$entity->id] = $entity;
                $newGroups[] = $id;
            });
            $entity->group = $groupId;

            if ($entity instanceof Player) {
                $self->groups[$groupId]->players[] = $entity->id;
            }
        }
        return $newGroups;
    }

    /**
     * 
     * @param type $groupId记录一个日志
     */
    public function logGroupPlayers($groupId) {
        echo "Players inside group " . $groupId . ":";
    }


    /**
     * 处理实体分组成员关系
     * @param \Server\Entity $entity
     * @return boolean
     */
    public function handleEntityGroupMembership(\Server\Entity $entity) {
        $hasChangedGroups = false;
        if ($entity) {
            $groupId = $this->map->getGroupIdFromPosition($entity->x, $entity->y);
            if (empty($entity->group) || ($entity->group && $entity->group != $groupId)) {
                $hasChangedGroups = true;
                $this->addAsIncomingToGroup($entity, $groupId);
                $oldGroups = $this->removeFromGroups($entity);
                $newGroups = $this->addToGroup($entity, $groupId);

                if (count($oldGroups) > 0) {
                    $entity->recentlyLeftGroups = array_diff($oldGroups, $newGroups);
                    //echo "group diff: " . json_encode($entity->recentlyLeftGroups);
                }
            }
        }
        return $hasChangedGroups;
    }

    /**
     * 过程组
     */
    public function processGroups() {
        $self = $this;

        if ($this->zoneGroupsReady) {
            $this->map->forEachGroup(function($id) use($self) {
                $spawns = array();
                if ($self->groups[$id]->incoming) {
                    foreach ($self->groups[$id]->incoming as $entity) {
                        if ($entity instanceof Player) {
                            $self->pushToGroup($id, new Messages\Spawn($entity), $entity->id);
                        } else {
                            $self->pushToGroup($id, new Messages\Spawn($entity));
                        }
                    }
                    foreach ($self->groups[$id]->incoming as $entity) {
                        if ($entity instanceof Player) {
                            $self->pushToGroup($id, new Messages\Spawn($entity), $entity->id);
                        } else {
                            $self->pushToGroup($id, new Messages\Spawn($entity));
                        }
                    }
                    $self->groups[$id]->incoming = array();
                }
            });
        }
    }
    /**
     * 移动 实体
     * @param type $entity 实体
     * @param type $x 位置x
     * @param type $y 位置y
     */
    public function moveEntity($entity, $x, $y) {
        if ($entity) {
            $entity->setPosition($x, $y);
            $this->handleEntityGroupMembership($entity);
        }
    }

    /**
     * 处理物品消失, 延迟1秒 闪烁, 4秒 消失
     * @param \Server\Item $item
     */
    public function handleItemDespawn(\Server\Item $item) {
        $self = $this;

        if ($item) {
            $item->handleDespawn(array(
                'beforeBlinkDelay' => 10000,
                'blinkCallback' => function()use($self, $item) {
                    // 发送给相邻的分组,物品闪烁
                    $self->pushToAdjacentGroups($item->group, new Messages\Blink($item));
                },
                'blinkingDuration' => 4000,
                'despawnCallback' => function()use($self, $item) {
                    // 发送给相邻的分组,物品消息
                    $self->pushToAdjacentGroups($item->group, new Messages\Destroy($item));
                    // 移除一个实体
                    $self->removeEntity($item);
                }
            ));
        }
    }

    public function handleEmptyMobArea($area) {
        
    }

    public function handleEmptyChestArea($area) {
        if ($area) {
            $chest = $this->addItem($this->createChest($area->chestX, $area->chestY, $area->items));
            $this->handleItemDespawn($chest);
        }
    }
 
    /**
     * 处理箱子开启逻辑
     * @param \Server\Chest $chest
     * @param \Server\Player $player
     */
    public function handleOpenedChest(\Server\Chest $chest, \Server\Player $player) {
        // 发送到相邻的分组
        $this->pushToAdjacentGroups($chest->group, $chest->despawn());
        // 移除一个实体
        $this->removeEntity($chest);
        // 获取箱子内的物品种类
        $kind = $chest->getRandomItem();
        if ($kind) {
            // 从箱子中开机一个物品
            $item = $this->addItemFromChest($kind, $chest->x, $chest->y);
            // 处理物品消失 1,4秒
            $this->handleItemDespawn($item);
        }
    }
    /**
     * 尝试向物品区域增加暴徒(怪物)
     * @param \Server\Mob $mob
     */
    public function tryAddingMobToChestArea(\Server\Mob $mob) {
        
        /**
         * @var \Server\ChestArea $area
         */
        foreach ($this->chestAreas as $area) {
           
            if ($area->contains($mob)) {
                $area->addToArea($mob);
            }
        }
    }

    public function updatePopulation($totalPlayers) {
        $this->pushBroadcast(new Messages\Population($this->playerCount, $totalPlayers ? $totalPlayers : $this->playerCount));
    }

    public function onConnect($connection) {
        $connection->onWebSocketConnect = array($this, 'onWebSocketConnect');
    }

    public function onWebSocketConnect($connection) {
        
    }

}
